#!/usr/bin/env python
#@+leo-ver=4
#@+node:@file mount.fuse
#@@first #!/usr/bin/env python

"""
This utility allows Gmail filesystems to be mounted with the regular
'mount' command, or even to be listed in /etc/fstab

Usage:
     1.  mount.gmailfs none /path/of/mount/point [options]
     2.  in /etc/fstab, add:
           none /path/of/mount/point gmailfs noauto[,options]
"""

import sys, os, os.path, time, optparse, getpass, re, tempfile, signal

DEFAULT_GMAILFS_LOCATION     = '/usr/share/gmailfs/gmailfs.py'
# delay (in seconds) after which we consider gmailfs could not mount, and
# we abort.
GMAILFS_MOUNTING_MAX_DELAY   = 12 
# delay (in seconds) between two consecutive checks for encfs still being
# mounted.                                
ENCFS_MOUNTPOINT_CHECK_DELAY = 5  # in seconds

def parseCommandLineArgs(args):
  usage_ = "usage: %prog none mountpoint [options]"

  parser = optparse.OptionParser(usage=usage_)
  parser.add_option("-p", "--prompt-for-password", dest="promptForPasswd",
                    action="store_true", default=False,
                    help="Prompt for the Gmail password" )
  parser.add_option("-e", "--use-transparent-encryption", dest="useEncfs",
                    action="store_true", default=False,
                    help="Use transparent encryption" )
  parser.add_option("-o", "--options", dest="gmailfsOptions",
                    help="Use those options", metavar="option1=value1,[option2=value2,...]",
                    default="")

  options, args = parser.parse_args(args)

  if len(args) == 2:
    pyfile, mountpoint = args
  else:
    parser.error("Wrong number of arguments")
    
  odata = options.gmailfsOptions

  namedOptions = {}
  if odata:
    for o in odata.split(","):
     try:
       k, v = o.split("=", 1)
       namedOptions[k] = v
     except:
       print "Ignored option :"+o

  if options.promptForPasswd:
    namedOptions['password'] = getpass.getpass("Gmail password: ")
    
  # pyfile sanitation
  if pyfile == 'none':
    pyfile = DEFAULT_GMAILFS_LOCATION
  else:
    pyfile = os.path.abspath(pyfile)
  if not os.path.isfile(pyfile):
    log.error("file %s doesn't exist, or is not a file" % pyfile)
    sys.exit(1)

  return pyfile, mountpoint, namedOptions, options.useEncfs

def main(mountpoint, namedOptions, useEncfs):
  if useEncfs: # create a temp. dir to mount the encrypted gmailfs volume
    clearMountPoint = mountpoint
    mountpoint = tempfile.mkdtemp()

  # Just run forever, don't fork. -- csm
  gmailfsPID = 0
  if gmailfsPID == 0:
    # this is the child process. We register a log-and-exit handler for
    # SIGHUP, then we start the actual child task: mounting the gmailfs
    childPID = os.getpid()
    log.info('Starting gmailfs in child process (PID %s)' % childPID)

    def handlerHUP(signum, frame):
      log.warning("Child process %s received SIGHUP, exiting..." % childPID)
      os._exit(1)
    signal.signal(signal.SIGHUP, handlerHUP)

    sys.argv = [] # blank the command-line params for this child
    gmailfs.main(mountpoint, namedOptions)
    log.info('gmailfs terminated')
  else:
    # this is the parent process. First we wait for gmailfs to become mounted
    # within GMAILFS_MOUNTING_MAX_DELAY seconds.
    # If this doesn't happen, we cleanup and exit.
    log.info('waiting for %s to become a mountpoint' % mountpoint)
    for i in range(GMAILFS_MOUNTING_MAX_DELAY): # give our child some time
      if not os.path.ismount(mountpoint): # not yet mounted
        # let's check our child is still alive, without blocking
        pid, status = os.waitpid(gmailfsPID, os.WNOHANG)
        if not pid == 0: # it died, let's exit
          log.error('gmailfs child died, exiting...')
          sys.exit(1)
      else: # success, gmailfs got mounted
        break
      time.sleep(1)

    if os.path.ismount(mountpoint):
      log.info('%s is now a mountpoint' % mountpoint)
    else: # gmailfs wasn't mounted in the allocated time frame, abort
      log.error("gmailfs did not connect in less than %s seconds, aborting..." % GMAILFS_MOUNTING_MAX_DELAY)
      os.kill(gmailfsPID, signal.SIGHUP) # send SIGHUP to our child
      os.waitpid(gmailfsPID, 0) # upon receiving SIGHUP, it should exit, so we block-wait on it
      log.info('Successfully reaped child %s' % gmailfsPID)
      sys.exit(3)
      
    if useEncfs:
      # encfs was requested. The first thing we do is define a cleanup()
      # function to unmount the actual gmailfs volume and remove the temporary
      # mountpoint.
      # We then spawn a child to run encfs, and wait for the encfs process
      # to either return an error (in which case we cleanup and
      # exit), or background itself (in which case we fork a child that
      # periodically checks if encfs is still mounted, and deos the cleanup
      # when it's not anymore.
      
      def cleanup(exitCode):
        log.info('encfs hook: running fusermount -u %s' % mountpoint)
        os.spawnvp(os.P_WAIT, 'fusermount', ('fusermount', '-u', mountpoint))
	log.info('encfs hook: removing intermediate mountpoint %s' % mountpoint)
        os.rmdir(mountpoint)
	log.info('encfs hook: cleanup complete, exiting')
        sys.exit(exitCode)

      rc = os.spawnvp(os.P_WAIT, 'encfs', ('encfs', mountpoint, clearMountPoint))
      if not rc == 0:
	log.error('encfs error (RC = %s), exiting...' % rc)
	cleanup(2)
      else:
        log.info('encfs backgrounded')

      if os.fork() == 0:
        # this is the child that will periodically monitor encfs
        while os.path.ismount(clearMountPoint):
          log.debug('waiting for %s to become unmounted' % clearMountPoint)
          time.sleep(ENCFS_MOUNTPOINT_CHECK_DELAY)
        log.info('%s is not a mountpoint anymore, which means encfs has exited' % clearMountPoint)
        cleanup(0)
      else: # let the parent exit
        pass
    else: # encfs not requested, let the parent exit
      pass

if __name__ == '__main__':
  pyfile, mountpoint, namedOptions, useEncfs = parseCommandLineArgs(sys.argv[1:])

  # import gmailfs from the pyfile location
  sys.path.append(os.path.abspath(os.path.dirname(pyfile)))
  import gmailfs

  # use the gmailfs log facility
  log = gmailfs.log

  main(mountpoint, namedOptions, useEncfs)

#@-node:@file mount.fuse
#@-leo
